Qhue experiments

Experiments with the Qhue python module.

If you haven't already, then pip install qhue before starting.

Some of these examples may assume you have a recent bridge with recent software.

If you're viewing this with my sample output, I've truncated some of it for readability. I have a lot of lights!

Basics


In [49]:
# Put in the IP address of your Hue bridge here
BRIDGE_IP='192.168.0.45'

from qhue import Bridge, QhueException, create_new_username

In [54]:
# If you have a username set up on your bridge, enter it here
# otherwise leave it as None and you'll be prompted to create one.
# e.g.:
# username='zeZomfNu-y-p1PLM9oeYTiXbtqsxn-q1-7RNLI4B'
username=None

if username is None:
    username = create_new_username(BRIDGE_IP)
    print("New user: {} . Put this in the username variable above.".format(username))

Let's get the numbers and names of the lights:


In [55]:
bridge = Bridge(BRIDGE_IP, username)
lights = bridge.lights()
for num, info in lights.items():
    print("{:16} {}".format(info['name'], num))


Kitchen Sink     3
Landing          4
Top Landing      5
Kitchen Stove    6
Front hall 1     7
Front hall 2     8
...

Let's try interactively changing a light. You could make this a lot more sophisticated:


In [56]:
from ipywidgets import interact, interactive, fixed
import ipywidgets as widgets

def setlight(lightid='14', on=True, ct=128, bri=128):
    bridge.lights[lightid].state(on=on)
    if on:
        bridge.lights[lightid].state(bri=bri, ct=ct)

light_list = interact(setlight,
                      lightid = widgets.Dropdown(
                            options={ lights[i]['name']:i for i in lights },
                            value='14',
                            description='Light:',
                        ),
                      on = widgets.Checkbox(value=True, description='On/off'),
                      bri = widgets.IntSlider(min=0,max=255,value=128, description='Bright:'),
                      ct = widgets.IntSlider(min=0,max=255,value=128, description='Colour:'))


The YAML format is a nice way to view the sometimes large amount of structured information which comes back from the bridge.

If you haven't got the Python yaml module, pip install PyYAML.


In [57]:
import yaml
print("{} lights:\n".format(len(lights)))
print(yaml.safe_dump(lights, indent=4))


20 lights:

'11':
    manufacturername: Philips
    modelid: LCT007
    name: Kitchen table
    state:
        alert: none
        bri: 169
        colormode: xy
        ct: 410
        effect: none
        hue: 14164
        'on': false
        reachable: true
        sat: 178
        xy: [0.4837, 0.4144]
    swupdate: {lastinstall: null, state: noupdates}
    swversion: 5.50.1.19085
    type: Extended color light
    uniqueid: 00:17:88:01:00:f7:e8:58-0b
'12':
    manufacturername: Philips
    modelid: LCT007
    name: Kitchen centre
    state:
        alert: none
        bri: 240
        colormode: xy
        ct: 382
        effect: none
        hue: 14665
        'on': false
        reachable: true
        sat: 156
        xy: [0.4677, 0.4121]
    swupdate: {lastinstall: null, state: noupdates}
    swversion: 5.50.1.19085
    type: Extended color light
    uniqueid: 00:17:88:01:00:f6:e4:98-0b
...


In [58]:
print(yaml.safe_dump(bridge.lights['3'](), indent=4))


manufacturername: Philips
modelid: LCT001
name: Kitchen Sink
state:
    alert: none
    bri: 240
    colormode: xy
    ct: 343
    effect: none
    hue: 15360
    'on': false
    reachable: true
    sat: 119
    xy: [0.4436, 0.4062]
swupdate: {lastinstall: null, state: noupdates}
swversion: 5.23.1.13452
type: Extended color light
uniqueid: 00:17:88:01:00:d3:13:6c-0b

Scenes

Let's look at the scenes defined in the bridge, and their IDs. Some of these may be created manually, and others by the Hue app or other software.

Version 1-type scenes just refer to the lights - each light is told: "Set the value you have stored for this scene".

Version 2 scenes have more details stored in the hub, which is generally more useful.


In [59]:
scenes = bridge.scenes()
print("{} scenes:\n".format(len(scenes)))
print(yaml.safe_dump(scenes, indent=4))


141 scenes:

19JUE2wqOsdtine:
    appdata: {data: TOScI_r04_d99, version: 1}
    lastupdated: '2017-02-25T20:07:42'
    lights: ['3', '6', '11', '12', '15', '16', '17', '18', '27', '34']
    locked: false
    name: Dining 3
    owner: IneFZ4CIEdSQQN4oCGExhsi0cWquxMrZY6tEElKM
    picture: ''
    recycle: false
    version: 2
342dc4014-on-0:
    appdata: {}
    lastupdated: null
    lights: ['4', '5']
    locked: false
    name: Landing low glow
    owner: none
    picture: ''
    recycle: true
    version: 1
351acdcd6-on-0:
    appdata: {}
    lastupdated: null
    lights: ['7', '8']
    locked: true
    name: Hall low glow on
    owner: none
    picture: ''
    recycle: true
    version: 1
SUThT3XiV7sSzml:
    appdata: {data: VKza7_r06_d99, version: 1}
    lastupdated: '2017-02-01T07:54:30'
    lights: ['14', '21', '32', '33']
    locked: true
    name: Warm
    owner: IneFZ4CIEdSQQN4oCGExhsi0cWquxMrZY6tEElKM
    picture: ''
    recycle: false
    version: 2
...

Details of a particular scene from the list:


In [60]:
print(yaml.safe_dump(bridge.scenes['wVXtOrFmdnySqUz']()))


appdata: {data: skbwq_r06_d06, version: 1}
lastupdated: '2017-02-01T07:54:30'
lights: ['14', '21', '32', '33']
lightstates:
  '14': {bri: 77, ct: 366, 'on': true}
  '21': {bri: 77, ct: 366, 'on': true}
  '32': {bri: 77, ct: 367, 'on': true}
  '33': {bri: 77, ct: 367, 'on': true}
locked: false
name: Dimmed
owner: IneFZ4CIEdSQQN4oCGExhsi0cWquxMrZY6tEElKM
picture: ''
recycle: false
version: 2

Let's list scenes with IDs, last updated time, and the lights affected:


In [61]:
for sid, info in scenes.items():
    print("\n{:16} {:20} {}".format( sid, info['name'], info['lastupdated']))
    for li in info['lights']:
        print("{:40}- {}".format('', lights[li]['name']))


SePln7Lt9-H7Hm7  Bright               2017-02-01T09:52:06
                                        - Sitting room 2
                                        - Sitting room 1
                                        - Mantelpiece R
                                        - Mantelpiece L

7264de849-on-0   Hall low glow on     None
                                        - Front hall 1
                                        - Front hall 2

77470a3f5-off-0  2 lights off         None
                                        - Front hall 1
                                        - Front hall 2

 ...

Tidying things up; let's delete a scene:


In [62]:
# Uncomment and edit this if you actually want to run it!
# print(bridge.scenes['cd06c70f7-on-0'](http_method='delete'))

Show the details of the scenes that affect a particular light:


In [63]:
lightname = 'Sitting room 1'
# How's this for a nice use of python iterators?
light_id = next(i for i,info in lights.items() if info['name'] == lightname)
print("Light {} - {}".format(light_id, lightname))
for line in ["{} : {:20} {}".format(sid, info['name'], info['lastupdated']) for sid, info in scenes.items() if light_id in info['lights']]:
    print(line)


Light 21 - Sitting room 1
SePln7Lt9-H7Hm7 : Bright               2017-02-01T09:52:06
VaknVPkUZnSrdiB : Bright               2017-02-01T07:54:30
3owQUn01W7nVsxR : Evening              2017-02-01T07:54:29
GYOWpf6lHjaVc3T : Off                  2016-09-13T21:11:19
wG25IXpcHHTim4g : Off                  2017-02-01T07:54:30
SUThT3XiV7sSzml : Warm                 2017-02-01T07:54:30
KZNM2DZmdcydRIc : All warm             2016-08-06T23:50:55
IB57U3scrj4cQWk : Read                 2017-02-01T07:54:29
wVXtOrFmdnySqUz : Dimmed               2017-02-01T07:54:30
YDfVlYFWoaL6yv5 : Nightlight           2017-02-01T07:54:29

Groups and rooms

Let's look at groups:


In [64]:
print(yaml.safe_dump(bridge.groups(), indent=4))


'1':
    action:
        alert: none
        bri: 240
        colormode: xy
        ct: 343
        effect: none
        hue: 15360
        'on': false
        sat: 119
        xy: [0.4436, 0.4062]
    lights: ['3', '6']
    name: Kitchen
    recycle: false
    state: {all_on: false, any_on: false}
    type: LightGroup
'2':
    action:
        alert: none
        bri: 126
        colormode: xy
        ct: 267
        effect: none
        hue: 16528
        'on': false
        sat: 29
        xy: [0.3944, 0.385]
    lights: ['7', '8']
    name: Hall
    recycle: false
    state: {all_on: false, any_on: false}
    type: LightGroup
'3':
    action:
        alert: none
        bri: 240
        colormode: xy
        ct: 343
        effect: none
        hue: 15360
        'on': false
        sat: 119
        xy: [0.4436, 0.4062]
    lights: ['12', '11', '6', '3']
    name: Dimmer 11
    recycle: false
    state: {all_on: false, any_on: false}
    type: LightGroup
...

The current Hue software creates 'rooms', which are groups with a type value set to Room:


In [65]:
groups = bridge.groups()
rooms = [(gid, info['name']) for gid, info in groups.items() if info.get('type') == 'Room' ]
for room_id, info in rooms:
    print("{:3} : {}".format(room_id, info))


4   : Kitchen
5   : Garden
6   : Sitting room
7   : Hall
8   : Landing
9   : Bedroom

Sensors

Sensors are mostly switches, but a few other things come under the same category in the bridge. There's a 'daylight' sensor, implemented in software, for example, and various bits of state can also be stored here so they can be used in rule conditions later.


In [66]:
sensors = bridge.sensors()
summary = [(info['name'], i, info['type']) for i,info in sensors.items()]
# Sort by name
# Python 2: summary.sort(lambda a,b: cmp(a[0], b[0]))
# Python 3:
summary.sort(key = lambda a: a[0])
for n,i,t in summary:
    print("{:30} {:>3} {}".format(n,i,t))
    #print(bridge.sensors[i]())


Bedroom tap                      7 ZGPSwitch
Daylight                         1 Daylight
Dimmer Switch 11 SceneCycle     14 CLIPGenericStatus
Dimmer Switch 12 SceneCycle     13 CLIPGenericStatus
Dining Room                      9 ZGPSwitch
Hall                             8 ZGPSwitch
Hall dimmer                     12 ZLLSwitch
Hall sensor                     24 ZLLPresence
Hue ambient light sensor 1      17 ZLLLightLevel
Hue ambient light sensor 2      21 ZLLLightLevel
Hue ambient light sensor 3      25 ZLLLightLevel
Hue ambient light sensor 4      29 ZLLLightLevel
Hue temperature sensor 1        15 ZLLTemperature
Hue temperature sensor 2        19 ZLLTemperature
Hue temperature sensor 3        23 ZLLTemperature
Hue temperature sensor 4        27 ZLLTemperature
Kitchen dimmer                  11 ZLLSwitch
Kitchen tap                      2 ZGPSwitch
Landing sensor                  16 ZLLPresence
Landing tap                      3 ZGPSwitch
Laundry sensor                  28 ZLLPresence
Laundry tap                      4 ZGPSwitch
MotionSensor 16.Companion       35 CLIPGenericStatus
MotionSensor 20.Companion       22 CLIPGenericStatus
MotionSensor 24.Companion       26 CLIPGenericStatus
MotionSensor 28.Companion       36 CLIPGenericStatus
Sitting room                    10 ZGPSwitch
Top Landing sensor              20 ZLLPresence
Top Tap                          6 ZGPSwitch
XFDani[4][1]sn:step             32 CLIPGenericStatus
XFDani[4]sn:state               31 CLIPGenericStatus

Here's a more complete list:


In [67]:
print(yaml.safe_dump(bridge.sensors(), indent=4))


'1':
    config: {configured: false, 'on': true, sunriseoffset: 30, sunsetoffset: -30}
    manufacturername: Philips
    modelid: PHDL00
    name: Daylight
    state: {daylight: null, lastupdated: none}
    swversion: '1.0'
    type: Daylight
'10':
    config: {'on': true}
    manufacturername: Philips
    modelid: ZGPSWITCH
    name: Sitting room
    state: {buttonevent: 34, lastupdated: '2017-07-04T22:03:46'}
    swupdate: {lastinstall: null, state: notupdatable}
    type: ZGPSwitch
    uniqueid: 00:00:00:00:00:41:1f:34-f2
'11':
    config:
        battery: 84
        'on': true
        pending: []
        reachable: true
    manufacturername: Philips
    modelid: RWL021
    name: Kitchen dimmer
    state: {buttonevent: 4002, lastupdated: '2017-07-04T09:42:08'}
    swupdate: {lastinstall: null, state: noupdates}
    swversion: 5.45.1.17846
    type: ZLLSwitch
    uniqueid: 00:17:88:01:10:33:28:66-02-fc00
...

Rules

Rules map sensor events etc. to actions.


In [68]:
rules = bridge.rules()
print(yaml.safe_dump(rules, indent=4))


'1':
    actions:
    -   address: /groups/4/action
        body: {scene: HfANai28yTRy07O}
        method: PUT
    conditions:
    - {address: /sensors/2/state/lastupdated, operator: dx}
    - {address: /sensors/2/state/buttonevent, operator: eq, value: '18'}
    created: '2016-09-23T09:10:49'
    lasttriggered: '2017-07-04T20:50:01'
    name: Tap 2.4
    owner: IneFZ4CIEdSQQN4oCGExhsi0cWquxMrZY6tEElKM
    recycle: false
    status: enabled
    timestriggered: 3
'10':
    actions:
    -   address: /groups/8/action
        body: {scene: zvuMOXo8vmShFZK}
        method: PUT
    conditions:
    - {address: /sensors/4/state/lastupdated, operator: dx}
    - {address: /sensors/4/state/buttonevent, operator: eq, value: '18'}
    created: '2016-07-23T11:48:50'
    lasttriggered: none
    name: Tap 4.4
    owner: IneFZ4CIEdSQQN4oCGExhsi0cWquxMrZY6tEElKM
    recycle: false
    status: enabled
    timestriggered: 0
...

Show the rules triggered by the Sitting Room switch.

For Tap switches, buttons 1,2,3,4 are represented by the values 34,16,17,18 respectively.


In [69]:
switch = '10'  # sitting room
print("Switch {} -- {}\n".format(switch, sensors[switch]['name']))

# State changes on the switch will look like this:
state_string = "/sensors/{}/state/".format(switch)

# Look through the rules for once which contain this 
# string in their conditions:
for rid, info in rules.items():
    this_switch = False
    matching_conditions = [c for c in info['conditions'] if state_string in c['address']]
    if len(matching_conditions) > 0:
        print("{:3} {:20}".format(rid, info['name']))
        for c in info['conditions']:
            print("   ? condition {}".format(c))
        for a in info['actions']:

            # If the action involves applying a scene, get its name
            scene_name = ""
            if 'scene' in a['body']:
                scene_name = scenes[a['body']['scene']]['name']
            
            print("   - action address {} body {!s:29s} {} ".format( a['address'], a['body'], scene_name))


Switch 10 -- Sitting room

29  Tap 10.3            
   ? condition {'address': '/sensors/10/state/lastupdated', 'operator': 'dx'}
   ? condition {'address': '/sensors/10/state/buttonevent', 'operator': 'eq', 'value': '17'}
   - action address /groups/6/action body {'scene': 'SUThT3XiV7sSzml'}  Warm 
30  Tap 10.2            
   ? condition {'address': '/sensors/10/state/lastupdated', 'operator': 'dx'}
   ? condition {'address': '/sensors/10/state/buttonevent', 'operator': 'eq', 'value': '16'}
   - action address /groups/6/action body {'scene': 'SePln7Lt9-H7Hm7'}  Bright 
31  Tap 10.4            
   ? condition {'address': '/sensors/10/state/lastupdated', 'operator': 'dx'}
   ? condition {'address': '/sensors/10/state/buttonevent', 'operator': 'eq', 'value': '18'}
   - action address /groups/6/action body {'scene': '3owQUn01W7nVsxR'}  Evening 
32  2:huelabs/tap-toggle
   ? condition {'address': '/sensors/10/state/buttonevent', 'operator': 'eq', 'value': '34'}
   ? condition {'address': '/sensors/10/state/lastupdated', 'operator': 'dx'}
   ? condition {'address': '/groups/6/state/any_on', 'operator': 'eq', 'value': 'false'}
   - action address /groups/6/action body {'on': True}                   
98  2:huelabs/tap-toggle
   ? condition {'address': '/sensors/10/state/buttonevent', 'operator': 'eq', 'value': '34'}
   ? condition {'address': '/sensors/10/state/lastupdated', 'operator': 'dx'}
   ? condition {'address': '/groups/6/state/any_on', 'operator': 'eq', 'value': 'true'}
   - action address /groups/6/action body {'on': False}                  

Let's see what is actually done by one of these scenes:


In [70]:
scene='3owQUn01W7nVsxR' # 'Evening' scene button 10.4

s = bridge.scenes[scene]()
print(yaml.safe_dump(s, indent=4))


appdata: {data: mdVDQ_r06_d99, version: 1}
lastupdated: '2017-02-01T07:54:29'
lights: ['14', '21', '32', '33']
lightstates:
    '14':
        bri: 189
        'on': true
        xy: [0.5102, 0.3642]
    '21': {bri: 189, 'on': true}
    '32': {bri: 189, 'on': true}
    '33': {bri: 189, 'on': true}
locked: true
name: Evening
owner: IneFZ4CIEdSQQN4oCGExhsi0cWquxMrZY6tEElKM
picture: ''
recycle: false
version: 2


In [ ]: